package net.gdface.utils;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import com.google.common.base.Function;
import static com.google.common.base.Preconditions.checkNotNull;

import static com.google.common.base.MoreObjects.firstNonNull;

/**
 * Description: <br>
 * InvocationHandler implementation of <code>Annotation</code> that pretends it
 * is a "real" source code annotation.
 * <p>
 * based org.apache.bval.jsr303.xml.AnnotationProxy(org.apache.bval:bval-jsr-303:0.5)
 */
public class AnnotationProxy<A extends Annotation> implements Annotation, InvocationHandler, Serializable {

    /** Serialization version */
    private static final long serialVersionUID = 1L;

    private final Class<A> annotationType;
    /** 定义的字段值 */
    private final Map<String, Object> values = new TreeMap<String, Object>();
    private final Map<String, Object> elements = new TreeMap<String, Object>();
    private final Map<String, Object> defaultValues = new TreeMap<String, Object>();
    @SuppressWarnings("rawtypes")
	private final LinkedHashMap<String, Class> importedClasses = new LinkedHashMap<>();
    @SuppressWarnings("rawtypes")
    private static final TreeMap<String, Class> globalImportedClasses = new TreeMap<>();

    /**
     * Create a new AnnotationProxy instance.
     * @param annot
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
	public AnnotationProxy(A annot) {
        this.annotationType = (Class<A>) checkNotNull(annot,"annot is null").annotationType();
        //values = getAnnotationValues(descriptor);
        
        // Obtain the "elements" of the annotation
        final Method[] methods = annot.annotationType().getDeclaredMethods();
        for (Method m : methods) {
            if (!m.isAccessible()) {
                m.setAccessible(true);
            }
            try {
                Object value = m.invoke(annot);
                if(value instanceof Annotation[]){
                	// 注解数数组类型
                	Annotation[] array = ((Annotation[])value);
                	AnnotationProxy[] proxys= new AnnotationProxy[array.length];
                	for(int i=0;i < array.length ;++i){
                		AnnotationProxy p = of(array[i]);
                		importedClasses.putAll(p.importedClasses);
                		importedClasses.put(p.annotationType.getName(),p.annotationType);
                		proxys[i]= p;
                	}
                	value = proxys;
                }
                this.elements.put(m.getName(), value);
                this.defaultValues.put(m.getName(), m.getDefaultValue());
            	if(!Objects.deepEquals(value, m.getDefaultValue())){
            		values.put(m.getName(), value);
            		createImport(value);
            	}
            } catch (Exception e) {
                throw new RuntimeException("Cannot access annotation " + annot + " element: " + m.getName(), e);
            } 
        }
        globalImportedClasses.putAll(importedClasses);
    }
    
    /**
     * 为注解对象创建{@link AnnotationProxy}实例,如果注解对象已经是{@link AnnotationProxy}实例则返回,
     * 如果如果注解对象已经的代理对象({@link InvocationHandler})是{@link AnnotationProxy}实例,则返回代理对象
     * @param annot 为{@code null}返回{@code null}
     * @return AnnotationProxy 实例
     */
    @SuppressWarnings("unchecked")
	public static <A extends Annotation>AnnotationProxy<A> of(A annot) {
    	if(null == annot){
    		return null;
    	}
    	try {
    		if(annot instanceof AnnotationProxy){
    			return (AnnotationProxy<A>) annot;
    		}
    		Object handler = Proxy.getInvocationHandler(annot);
	    	if(handler instanceof AnnotationProxy){
	    		return (AnnotationProxy<A>) handler;
	    	}
		} catch (Exception e) {
		}
    	return new AnnotationProxy<A>(annot);
	}
    @SuppressWarnings("rawtypes")
	public static Collection<Class> getGlobalImportedClasses() {
		return globalImportedClasses.values();
	}
	public static Set<String> getGlobalImportedClassNames() {
		return globalImportedClasses.keySet();
	}
    public static void clearGlobalImportedClasses() {
    	globalImportedClasses.clear();
    }
    /**
	 * (递归)将注释对象中所有引用的类添加到{@link #importedClasses}
	 * @param value
	 */
	@SuppressWarnings({ "rawtypes" })
	private void createImport(Object value){
		if(value instanceof Class){
			importedClasses.put(((Class)value).getName(), (Class)value);
		}else if (value instanceof Enum){
			createImport(value.getClass());
		}else if (value instanceof Class[]){
			for(Class clazz:(Class[])value){
				createImport(clazz);
			}
		}else if (value instanceof Enum[]){
			createImport(value.getClass().getComponentType());
		}
	}

	@SuppressWarnings("rawtypes")
    public LinkedHashSet<Class> getImportedClasses() {
    	return new LinkedHashSet<>(importedClasses.values());
    }
    
	/**
     * {@inheritDoc}
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	return values.get(method.getName());
//        if (values.containsKey(method.getName())) {
//        }
//        return method.invoke(this, args);
    }

    /**
     * 设置指定的字段
     * @param key
     * @param value
     */
    public void setValue(String key, Object value) { 
    	if(null != key && elements.containsKey(key)){
    		values.put(key, value);    		
    	}
	}

	/** 
	 * 返回指定字段的值
	 * @param key
	 * @return 没找到返回{@code null}
	 */
	public Object getValue(String key) {
		return null == key ? null : values.get(key);
	}

	/**
	 * 返回所有字段的值
	 */
	public Map<String, Object> getValues() {
		return new HashMap<>(values);
	}

	/**
	 * 返回是否有定义字段
	 */
	public boolean isEmpty() {
		return values.isEmpty();
	}
    /**
     * Create the annotation represented by this builder.
     *
     * @return {@link Annotation}
     */
    public A createAnnotation() {
        ClassLoader classLoader = annotationType.getClassLoader();
        @SuppressWarnings("unchecked")
        final Class<A> proxyClass = (Class<A>) Proxy.getProxyClass(classLoader, annotationType);
        try{
        	Constructor<A> constructor = proxyClass.getConstructor(InvocationHandler.class);
        	return constructor.newInstance(this);
        } catch (Exception e) {
        	throw new RuntimeException("Unable to create annotation for configured constraint", e);
        }
    }
	/**
     * {@inheritDoc}
     */
    public Class<? extends Annotation> annotationType() {
        return annotationType;
    }

    private static final Function<Class<?>,String> TO_CLASS_NAME_FUN = new Function<Class<?>,String>(){
	
	@Override
	public String apply(Class<?> input) {
		return null == input ? null: input.getName();
	}};

	/**
	 * @since 3.1.0
	 */
	public String toString(Function<Class<?>,String> toClassNameFun ) {
		toClassNameFun = firstNonNull(toClassNameFun, TO_CLASS_NAME_FUN);
		StringBuilder result = new StringBuilder();
		result.append('@').append(annotationType().getSimpleName());
		SortedSet<String> methods = getMethodsSorted();
		if(!methods.isEmpty()){
			result.append('(');
			boolean comma = false;
			for (String m : methods) {
				if (comma)
					result.append(", ");
				
				result.append(m).append('=');
				Object value = values.get(m);
				appendValue(result,value, toClassNameFun);
				comma = true;
			}
			result.append(')');
		}
		return result.toString();
	}
	/**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return toString(TO_CLASS_NAME_FUN);
    }
    private void appendValue(StringBuilder result,Object value, Function<Class<?>,String> toClassNameFun ){
		if(null != value && value.getClass().isArray() && 1 == Array.getLength(value))
		{
			appendValue(result,Array.get(value, 0), toClassNameFun);
		}else  if (value instanceof boolean[]) 
	    {
	   		appendInBraces(result, Arrays.toString((boolean[])value));        		
	    }
	    else if (value instanceof byte[]) 
	    {
	       appendInBraces(result, Arrays.toString((byte[])value));
	    }
	    else if (value instanceof short[]) 
	    {
	       appendInBraces(result, Arrays.toString((short[])value));
	    }
	    else if (value instanceof int[]) 
	    {
	       appendInBraces(result, Arrays.toString((int[])value));
	    }
	    else if (value instanceof long[]) 
	    {
	       appendInBraces(result, Arrays.toString((long[])value));
	    }
	    else if (value instanceof float[]) 
	    {
	       appendInBraces(result, Arrays.toString((float[])value));
	    }
	    else if (value instanceof double[]) 
	    {
	       appendInBraces(result, Arrays.toString((double[])value));
	    }
	    else if (value instanceof char[]) 
	    {
	       appendInBraces(result, Arrays.toString((char[])value));
	    }
	    else if (value instanceof String[]) 
	    {
	       String[] strings = (String[]) value;
		   String[] quoted = new String[strings.length];
		   for(int j=0; j<strings.length; j++) 
		   {
			   quoted[j] = "\"" + strings[j] + "\"";
		   }
		   appendInBraces(result, Arrays.toString(quoted));
	    }
	    else if (value instanceof Class<?>[]) 
	    {
	       Class<?>[] classes = (Class<?>[]) value;
	       String[] names = new String[classes.length];
	       for(int j=0; j<classes.length; j++) 
	       {
	    	   names[j] = toClassNameFun.apply(classes[j]) + ".class";
	       }
	       appendInBraces(result, Arrays.toString(names));
	    }
	    else if (value instanceof AnnotationProxy<?>[]) 
	    {
	    	AnnotationProxy<?>[] proxies = (AnnotationProxy<?>[]) value;
	    	String[] names = new String[proxies.length];
	    	for(int j=0; j<proxies.length; j++) 
	    	{
	    		names[j] = proxies[j].toString(toClassNameFun);
	    	}
	    	appendInBraces(result, Arrays.toString(names));
	    }
	    else if (value instanceof Enum[]) 
	    {            	
	    	@SuppressWarnings("rawtypes")
			Enum[] classes = (Enum[]) value;
	    	String[] names = new String[classes.length];
	    	for(int j=0; j<classes.length; j++) 
	    	{
	    		names[j] = classes[j].getClass().getSimpleName()+"." + value;
	    	}
	    	appendInBraces(result, Arrays.toString(names));
	    }
	    else if (value instanceof Object[]) 
	    {
	       appendInBraces(result, Arrays.toString((Object[])value));
	    }
	    else if (value instanceof String) 
	    {
	       result.append('"').append(value).append('"');
	    }
	    else if (value instanceof Class<?>) 
	    {
	       result.append(toClassNameFun.apply((Class<?>)value)).append(".class");
	    }
	    else if (value instanceof Enum) 
	    {
	    	result.append(value.getClass().getSimpleName()).append(".").append(value);
	    }
	    else 
	    {
	       result.append(value);
	    }
	}

	private SortedSet<String> getMethodsSorted() {
        SortedSet<String> result = new TreeSet<String>();
        result.addAll(values.keySet());
        return result;
    }
    private void appendInBraces(StringBuilder buf, String s) {
        buf.append('{').append(s.substring(1,s.length()-1)).append('}');
     }
}
